/*-------------------------------------------------------------------------
 *
 * csvlog.c
 *	  CSV日志记录
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/error/csvlog.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/xact.h"
#include "libpq/libpq.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "postmaster/bgworker.h"
#include "postmaster/syslogger.h"
#include "storage/lock.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/backend_status.h"
#include "utils/elog.h"
#include "utils/guc.h"
#include "utils/ps_status.h"


/*
 * 将字符串的CSV版本附加到StringInfo
 * 我们使用PostgreSQL的CSV默认值，即quote = escape = '"'
 * 如果为NULL，则不附加任何内容。
 */
static inline void fc_appendCSVLiteral(StringInfo fc_buf, const char *fc_data)
{
	const char *fc_p = fc_data;
	char		fc_c;

	/* 避免将空字符串与NULL混淆 */
	if (fc_p == NULL)
		return;

	appendStringInfoCharMacro(fc_buf, '"');
	while ((fc_c = *fc_p++) != '\0')
	{
		if (fc_c == '"')
			appendStringInfoCharMacro(fc_buf, '"');
		appendStringInfoCharMacro(fc_buf, fc_c);
	}
	appendStringInfoCharMacro(fc_buf, '"');
}

/*
 * write_csvlog -- 生成并写入CSV日志条目
 *
 * 根据获得的Errordata构造错误消息，以CSV格式呈现，
 * 格式描述见doc/src/sgml/config.sgml。
 */
void write_csvlog(ErrorData *fc_edata)
{
	StringInfoData fc_buf;
	bool		fc_print_stmt = false;

	/* 行号的静态计数器 */
	static long fc_log_line_number = 0;

	/* 当前进程中的计数器是否已重置？ */
	static int	fc_log_my_pid = 0;

	/*
	 * 这是少数几个我们宁愿不从postmaster那里继承静态
	 * 变量值的地方之一。但由于我们会这样做，所以在
	 * MyProcPid更改时重置它。
	 */
	if (fc_log_my_pid != MyProcPid)
	{
		fc_log_line_number = 0;
		fc_log_my_pid = MyProcPid;
		reset_formatted_start_time();
	}
	fc_log_line_number++;

	initStringInfo(&fc_buf);

	/* 带毫秒的时间戳 */
	appendStringInfoString(&fc_buf, get_formatted_log_time());
	appendStringInfoChar(&fc_buf, ',');

	/* 用户名 */
	if (MyProcPort)
		fc_appendCSVLiteral(&fc_buf, MyProcPort->user_name);
	appendStringInfoChar(&fc_buf, ',');

	/* 数据库名称 */
	if (MyProcPort)
		fc_appendCSVLiteral(&fc_buf, MyProcPort->database_name);
	appendStringInfoChar(&fc_buf, ',');

	/* 进程ID */
	if (MyProcPid != 0)
		appendStringInfo(&fc_buf, "%d", MyProcPid);
	appendStringInfoChar(&fc_buf, ',');

	/* 远程主机和端口 */
	if (MyProcPort && MyProcPort->remote_host)
	{
		appendStringInfoChar(&fc_buf, '"');
		appendStringInfoString(&fc_buf, MyProcPort->remote_host);
		if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
		{
			appendStringInfoChar(&fc_buf, ':');
			appendStringInfoString(&fc_buf, MyProcPort->remote_port);
		}
		appendStringInfoChar(&fc_buf, '"');
	}
	appendStringInfoChar(&fc_buf, ',');

	/* 会话ID */
	appendStringInfo(&fc_buf, "%" INT64_MODIFIER "x.%x", MyStartTime, MyProcPid);
	appendStringInfoChar(&fc_buf, ',');

	/* 行号 */
	appendStringInfo(&fc_buf, "%ld", fc_log_line_number);
	appendStringInfoChar(&fc_buf, ',');

	/* PS显示 */
	if (MyProcPort)
	{
		StringInfoData fc_msgbuf;
		const char *fc_psdisp;
		int			fc_displen;

		initStringInfo(&fc_msgbuf);

		fc_psdisp = get_ps_display(&fc_displen);
		appendBinaryStringInfo(&fc_msgbuf, fc_psdisp, fc_displen);
		fc_appendCSVLiteral(&fc_buf, fc_msgbuf.data);

		pfree(fc_msgbuf.data);
	}
	appendStringInfoChar(&fc_buf, ',');

	/* 会话开始时间戳 */
	appendStringInfoString(&fc_buf, get_formatted_start_time());
	appendStringInfoChar(&fc_buf, ',');

	/* 虚拟事务ID */
	/* 保持VXID格式与lockfuncs.c同步 */
	if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
		appendStringInfo(&fc_buf, "%d/%u", MyProc->backendId, MyProc->lxid);
	appendStringInfoChar(&fc_buf, ',');

	/* 事务ID */
	appendStringInfo(&fc_buf, "%u", GetTopTransactionIdIfAny());
	appendStringInfoChar(&fc_buf, ',');

	/* 错误严重性 */
	appendStringInfoString(&fc_buf, _(error_severity(fc_edata->elevel)));
	appendStringInfoChar(&fc_buf, ',');

	/* SQL状态代码 */
	appendStringInfoString(&fc_buf, unpack_sql_state(fc_edata->sqlerrcode));
	appendStringInfoChar(&fc_buf, ',');

	/* 错误消息 */
	fc_appendCSVLiteral(&fc_buf, fc_edata->message);
	appendStringInfoChar(&fc_buf, ',');

	/* 错误详情或错误详情日志 */
	if (fc_edata->detail_log)
		fc_appendCSVLiteral(&fc_buf, fc_edata->detail_log);
	else
		fc_appendCSVLiteral(&fc_buf, fc_edata->detail);
	appendStringInfoChar(&fc_buf, ',');

	/* 错误提示 */
	fc_appendCSVLiteral(&fc_buf, fc_edata->hint);
	appendStringInfoChar(&fc_buf, ',');

	/* 内部查询 */
	fc_appendCSVLiteral(&fc_buf, fc_edata->internalquery);
	appendStringInfoChar(&fc_buf, ',');

	/* 如果打印了内部查询，也打印内部位置 */
	if (fc_edata->internalpos > 0 && fc_edata->internalquery != NULL)
		appendStringInfo(&fc_buf, "%d", fc_edata->internalpos);
	appendStringInfoChar(&fc_buf, ',');

	/* 错误上下文 */
	if (!fc_edata->hide_ctx)
		fc_appendCSVLiteral(&fc_buf, fc_edata->context);
	appendStringInfoChar(&fc_buf, ',');

	/* 用户查询 --- 仅在未被调用者禁用时报告 */
	fc_print_stmt = check_log_of_query(fc_edata);
	if (fc_print_stmt)
		fc_appendCSVLiteral(&fc_buf, debug_query_string);
	appendStringInfoChar(&fc_buf, ',');
	if (fc_print_stmt && fc_edata->cursorpos > 0)
		appendStringInfo(&fc_buf, "%d", fc_edata->cursorpos);
	appendStringInfoChar(&fc_buf, ',');

	/* 文件错误位置 */
	if (Log_error_verbosity >= PGERROR_VERBOSE)
	{
		StringInfoData fc_msgbuf;

		initStringInfo(&fc_msgbuf);

		if (fc_edata->funcname && fc_edata->filename)
			appendStringInfo(&fc_msgbuf, "%s, %s:%d",
							 fc_edata->funcname, fc_edata->filename,
							 fc_edata->lineno);
		else if (fc_edata->filename)
			appendStringInfo(&fc_msgbuf, "%s:%d",
							 fc_edata->filename, fc_edata->lineno);
		fc_appendCSVLiteral(&fc_buf, fc_msgbuf.data);
		pfree(fc_msgbuf.data);
	}
	appendStringInfoChar(&fc_buf, ',');

	/* 应用程序名称 */
	if (application_name)
		fc_appendCSVLiteral(&fc_buf, application_name);

	appendStringInfoChar(&fc_buf, ',');

	/* 后端类型 */
	fc_appendCSVLiteral(&fc_buf, get_backend_type_for_log());
	appendStringInfoChar(&fc_buf, ',');

	/* 领导进程ID */
	if (MyProc)
	{
		PGPROC	   *fc_leader = MyProc->lockGroupLeader;

		/*
		 * 仅为活动的并行工作线程显示领导者。这将不包括
		 * 并行组的领导者。
		 */
		if (fc_leader && fc_leader->pid != MyProcPid)
			appendStringInfo(&fc_buf, "%d", fc_leader->pid);
	}
	appendStringInfoChar(&fc_buf, ',');

	/* 查询ID */
	appendStringInfo(&fc_buf, "%lld", (long long) pgstat_get_my_query_id());

	appendStringInfoChar(&fc_buf, '\n');

	/* 如果在syslogger进程中，尝试直接写入文件的消息 */
	if (MyBackendType == B_LOGGER)
		write_syslogger_file(fc_buf.data, fc_buf.len, LOG_DESTINATION_CSVLOG);
	else
		write_pipe_chunks(fc_buf.data, fc_buf.len, LOG_DESTINATION_CSVLOG);

	pfree(fc_buf.data);
}
